import numpy as np
import copy

class SA:
    def __init__(self,k=3,r=0.9) -> None:
        """
        SA 算法框架，继承此类可大大降低研究人员使用SA算法的难度

        继承注意：一般来说，继承此类之后，只需要重写(重载)get_near_area方法和get_value方法即可
        参数说明:
        k:连续迭代次数
        r:退火率
        """
        self.values=[]
        self.T=[]
        self.T0=-1
        self.k=k
        self.epoch=-1
        self.length=-1
        self.r=r
        self.data=None
        self.k_now=0
        self.best_value=1000
        self.x_list=[]

    def _principle_accept(self,last,now):
        if now<=last:
            print("{:.4f}|\t{:.4f}|".format(0.0000,0.0000),end="\t")
            return True
        else:
            p=np.exp(-np.abs(now-last)/self.T0)
            p0=np.random.rand()
            print("{:.4f}|\t{:.4f}|".format(p,p0),end="\t")
            if p>p0:
                return True
            else:
                return False

    def _update_temp(self):
        self.T0=self.r*self.T0

    def get_near_area(self,x):
        xs=list(self.data.keys())
        xs.remove(x)
        print(str(xs),end='\t')

        return np.random.choice(xs)
    

    def get_value(self,x):
        return self.data[x]


    def __call__(self,data,x0,epoch=100,T0=100) :
        """
        参数说明:
        data:输入的数据,为字典格式
        x0:初始x0
        epoch:迭代次数
        T0:初始温度

        example：
        data={"1":90,"2":60,"3":50,"4":80,"5":100,"6":40,"7":20,"8":70}
        model=SA(r=0.5)

        model(data=data,x0='1',epoch=10)
        """
        self.length=len(data)
        self.T0=T0
        self.data=data
        self.epoch=epoch
        f_now=self.get_value(x0)
        print("{:4s}\t{:4s}\t{:4s}\t{:^36s}\t{:4s}\t{:4s}".format('x','f','T','N(x)','P','P0'))
        print("{:4s}\t{:4s}".format(str(x0),str(f_now)))
        x_flag=True
        self._update_temp()
        ## 循环主体
        for _ in range(self.epoch):
            
            ## 记录上一个想x和f值，为f被拒绝做准备
            last_x0=copy.deepcopy(x0)
            f_last=copy.deepcopy(f_now)

            ## 如果接受f(k+1) 则从新邻域选择x
            
            if x_flag:
                x0=self.get_near_area(x0)
            
            f_now=self.get_value(x0)
            self.values.append(f_now)
            x_flag=True
            ## 记录最优值
            if f_now<self.best_value:
                self.best_value=f_now
            

            ## 判断是否满足接受准则
            flag=self._principle_accept(f_last,f_now)
            print()

            print("="*80)
            if flag:
                self.k_now+=1
                if self.k_now==self.k:
                    self.k_now=0
                    self._update_temp()
            ## 如果拒绝f(k+1) 则从原来邻域选择x
            else:
                x0=self.get_near_area(last_x0)
                x_flag=False
            print("{:4s}|\t{:4s}|\t{:4s}|\t{:^36s}|".format(str(x0),str(f_now),str(self.T0),str(self.x_list)),end='\t')

        print("最优值是{}".format(self.best_value))

